package com.htfyun.eink.pw.draw;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.htfyun.eink.pw.core.IOnDrawListener;
import com.htfyun.eink.pw.core.IPointerEventListener;
import com.htfyun.eink.pw.core.ctrl.PWCoreHelper;
import com.htfyun.eink.pw.core.enums.PWAction;
import com.htfyun.eink.pw.draw.cache.CacheHelper;
import com.htfyun.eink.pw.draw.listener.PWTouchEventListener;
import com.htfyun.eink.pw.draw.pen.IPen;
import com.htfyun.eink.pw.draw.pen.IPenEraser;
import com.htfyun.eink.pw.draw.pen.IPenShape;
import com.htfyun.eink.pw.draw.pen.IPenWriter;
import com.htfyun.eink.pw.draw.pen.PenDrawPointerResultEnums;
import com.htfyun.eink.pw.draw.pointer.PWPointer;
import com.htfyun.eink.pw.draw.pointer.PWPointerActionEnums;
import com.htfyun.eink.pw.impl.BuildConfig;
import com.htfyun.eink.pw.utils.DebugLog;
import com.htfyun.eink.pw.utils.PWUtils;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class PWDrawImpl extends PWDrawPropertyImpl implements IPWDraw, IOnDrawListener, IPointerEventListener {

    private final int[] locationOnScreen = new int[2];
    // view 手写 的 rect
    private final Rect bindViewPWRect = new Rect();
    // view draw rect
    private final Rect bindViewDrawingRect = new Rect();

    private boolean isPointerDownInView = false;
    private boolean isPointerDownInTopView = false;

    private final MotionEvent.PointerCoords mTempCoords = new MotionEvent.PointerCoords();

    private final PWPointer pwPointer = new PWPointer();
    private final VelocityTracker velocityTracker = VelocityTracker.obtain();

    private final Map<String, IPenWriter> penWriterMap = new HashMap<>();
    private final Map<String, IPenEraser> penEraserMap = new HashMap<>();

    private final Paint bitmapPaint = new Paint();

    @Nullable
    private IPen thePen;

    //拦截 pointerEvent 的 move action 的时间, 拦截成功后, 直接返回 false. 至于 invalidate 后, onDraw 才能得到回调.
    private long timeOfTryInterceptMoveEvent = System.currentTimeMillis();

    @Nullable
    private WeakReference<View> bindViewRef;

    public PWDrawImpl() {
        super();

        Log.i(TAG, "PWDrawImpl: versionName=" + BuildConfig.VERSION_NAME
                + ", versionCode=" + BuildConfig.VERSION_CODE
                + ", buildType=" + BuildConfig.BUILD_TYPE
        );

        bitmapPaint.setAntiAlias(true);
        bitmapPaint.setFilterBitmap(true);
        bitmapPaint.setDither(true);

    }

    @Override
    public void bindView(@NonNull View view) {
        if (getBindView() != null && (!Objects.equals(view, getBindView()))) {
            unbindView(getBindView());
            Log.w(TAG, "WARNING! before bindView view(" + view + "),  unbindView(" + getBindView() + ")");
        }

        bindViewRef = new WeakReference<>(view);
        configListenerForView(view);
        initWithView(view);

        //for cache
        String cacheDir = getPWConfig().configCache.getCacheDir();
        if (TextUtils.isEmpty(cacheDir)) {
            cacheDir = view.getContext().getCacheDir().getAbsolutePath();
        }
        CacheHelper.Instance.setCacheDir(cacheDir);
    }

    @Override
    public void unbindView(@NonNull View view) {
        if (!Objects.equals(view, getBindView())) {
            Log.w(TAG, "WARNING! the unbindView view(" + view + ") is not bindView(" + getBindView() + ")");
        }
        removeListenerForView(view);
        deinitWithView(view);

        bindViewRef = null;
    }

    @Nullable
    @Override
    public View getBindView() {
        if (bindViewRef == null) {
            return null;
        }
        return bindViewRef.get();
    }

    @Override
    public void clearAllBmp() {
        getCanvasPW().drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

        invalidate();
    }

    @Override
    public void clearMemory() {
        bitmapPool.clearMemory();
    }

    @Override
    public void registerPenWriter(@NonNull IPenWriter penWriter) {
        penWriterMap.put(penWriter.getPenId(), penWriter);
    }

    @Override
    public void unregisterPenWriter(@NonNull IPenWriter penWriter) {
        penWriterMap.remove(penWriter.getPenId());
    }

    @NonNull
    @Override
    public List<IPenWriter> getRegisteredPenWriterList() {
        return new ArrayList<>(penWriterMap.values());
    }

    @Override
    public void registerPenEraser(@NonNull IPenEraser penEraser) {
        penEraserMap.put(penEraser.getPenId(), penEraser);
    }

    @Override
    public void unregisterPenEraser(@NonNull IPenEraser penEraser) {
        penEraserMap.remove(penEraser.getPenId());
    }

    @NonNull
    @Override
    public List<IPenEraser> getRegisteredPenEraserList() {
        return new ArrayList<>(penEraserMap.values());
    }

    @Override
    public void computeClipOutPathByRects(@NonNull Path clipOutPath, @NonNull List<Rect> rects) {
        super.computeClipOutPathByRects(clipOutPath, rects);
        if (!clipOutPath.isEmpty()) {
            clipOutPath.offset(-bindViewPWRect.left, -bindViewPWRect.top);
        }
    }

    @Override
    public void onDraw(Canvas canvas) {
        try {
            Bitmap bmp = bmpPW;
            if (bmp != null && !bmp.isRecycled()) {
                canvas.drawBitmap(bmp, 0, 0, bitmapPaint);
            }

            if (thePen instanceof IPenShape) {
                IPenShape penShape = (IPenShape) thePen;
                canvas.drawPath(penShape.getFloatPath(), penShape.getFloatPaint());
            }
        } catch (Exception ignored) {

        }
    }

    /**
     * 从 sdk 那边回调的 onPointerEvent, 一切都是从这里开始的.<br>
     *
     * @param motionEvent The motion event to be dispatched.
     * @return true, 处理了, sdk 将不再下发; false, 没有处理, sdk 将继续下发
     */
    @Override
    public boolean onPointerEvent(MotionEvent motionEvent) {
        final int toolType = motionEvent.getToolType(0);
        final int action = motionEvent.getAction() & MotionEvent.ACTION_MASK;
        final PWPointerActionEnums actionEnums = PWPointerActionEnums.actionToPWPointerActionEnums(action);

        if (getPWConfig().configLog.isLogMotionEventDetails()) {
            Log.i(TAG, "motionEvent = " + motionEvent);
        }

        List<Integer> hoverAction = new ArrayList<>();
        hoverAction.add(MotionEvent.ACTION_HOVER_ENTER);
        hoverAction.add(MotionEvent.ACTION_HOVER_MOVE);
        hoverAction.add(MotionEvent.ACTION_HOVER_EXIT);
        boolean ignoreHover = toolType == MotionEvent.TOOL_TYPE_STYLUS
                && hoverAction.contains(motionEvent.getActionMasked());

        if (ignoreHover) {
            return false;
        }

        if (!getPWConfig().configPWEnabled.isPWEnabled()) {
            return false;
        }

        if (toolType == MotionEvent.TOOL_TYPE_UNKNOWN) {
            return false;
        }

        //只处理 电磁笔,手指, 擦除 toolType
        if (!(toolType == MotionEvent.TOOL_TYPE_FINGER
                || toolType == MotionEvent.TOOL_TYPE_STYLUS
                || toolType == MotionEvent.TOOL_TYPE_ERASER)) {
            return false;
        }

        if (toolType == MotionEvent.TOOL_TYPE_FINGER
                && !getPWConfig().configPointer.isFingerWritable()) {
            return false;
        }

        if (!PWUtils.canViewReceivePointerEvents(getBindView())) {
            return false;
        }

        //只拦截 move
        if (canInterceptMove(actionEnums)) {
            return false;
        }

        boolean handled = false;

        switch (actionEnums) {
            case DOWN:
                isPointerDownInView = bindViewPWRect.contains((int) motionEvent.getX(), (int) motionEvent.getY());
                isPointerDownInTopView = isPointerInClipOutRects(motionEvent);
                handled = isPointerDownInView && !isPointerDownInTopView;
                break;
            case MOVE:
                handled = isPointerDownInView && !isPointerDownInTopView;
                break;
            case UP:
                if (isPointerDownInView && !isPointerDownInTopView) {
                    handled = true;
                }
                isPointerDownInView = false;
                isPointerDownInTopView = false;
                PWCoreHelper.setPWAction(PWAction.CLEAR_FSB1);
            default:
                break;
        }

        if (getPWConfig().configLog.isLogMotionEventAction()) {
            Log.i(TAG, "action = " + MotionEvent.actionToString(action)
                    + ", toolType = " + PWPointer.toolTypeToString(motionEvent.getToolType(0))
                    + ", isPointerDownInView = " + isPointerDownInView
                    + ", isPointerDownInTopView = " + isPointerDownInTopView
                    + ", handled = " + handled);
        }

        if (handled) {

            long time = System.currentTimeMillis();

            final float offsetX = locationOnScreen[0];
            final float offsetY = locationOnScreen[1];
            motionEvent.offsetLocation(-offsetX, -offsetY);
            doOnPointerEvent(motionEvent);
            motionEvent.offsetLocation(offsetX, offsetY);

            if (getPWConfig().configLog.isLogHandlePointerUsedTime()) {
                Log.i(TAG, "doOnPointerEvent used time = " + (System.currentTimeMillis() - time));
            }
        }

        return handled && !getPWConfig().configPointer.isTouchDispatchEnabled();
    }

    /**
     * 是否 可以拦截 move
     *
     * @return true, 可以; false, 不可以
     */
    private boolean canInterceptMove(PWPointerActionEnums actionEnums) {
        if (actionEnums != PWPointerActionEnums.MOVE) {
            return false;
        }
        boolean canIntercept = (thePen instanceof IPenShape) || (thePen instanceof IPenEraser);
        if (!canIntercept) {
            return false;
        }
        long now = System.currentTimeMillis();
        if (now - timeOfTryInterceptMoveEvent <
                getPWConfig().configPointer.getIntervalTimeOfInterceptMoveForOnDraw()) {
            //如果这里不返回的话, invalidate 后, onDraw 不会回调.
            return true;
        }
        timeOfTryInterceptMoveEvent = now;
        return false;
    }

    /**
     * 判断 event 是否  Clip Out Rects 上, 即是否在 pw-view 之上的 top-view 或者 IME 上 <br>
     * 这里使用的是绝对屏幕 rect
     *
     * @param event {@link MotionEvent}
     * @return true, 该点在 Clip Out Rects 上; false, 没有在Clip Out Rects 上
     */
    private boolean isPointerInClipOutRects(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        for (Rect rect : getClipOutRects()) {
            if (rect.contains(x, y)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 测试时发现, 当是电磁笔笔帽时, 会收到 2 个 MotionEvent.ACTION_HOVER_ENTER 事件. 时序是: <br>
     * 1. down ->  x[0]=187.0903, y[0]=497.87402;<br>
     * 2. move ->  x[0]=187.0903, y[0]=497.87402;<br>
     * 3. down ->  x[0]=188.51035, y[0]=498.7616 <br>
     *
     * @param event The motion event to be dispatched.
     * @return true, 相同的点; false, 不同的点
     */
    private boolean isSameDownActionComparingWithPreviousPointer(MotionEvent event) {

        if (pwPointer.toolType != event.getToolType(0)) {
            return false;
        }

        final PWPointerActionEnums action = PWPointerActionEnums.actionToPWPointerActionEnums(event.getAction() & MotionEvent.ACTION_MASK);

        if (pwPointer.x == event.getX()
                && pwPointer.y == event.getY()
                && action != PWPointerActionEnums.UP) {
            return true;
        }

        if (action == PWPointerActionEnums.MOVE) {
            return false;
        }

        return pwPointer.toPWPointerActionEnums() == action;
    }

    /**
     * 目前只支持一个点
     *
     * @param event 需要处理的 {@link MotionEvent}
     */
    private void doOnPointerEvent(@NonNull MotionEvent event) {
        if (isSameDownActionComparingWithPreviousPointer(event)) {
            DebugLog.e("same event = " + event);
            return;
        }
        final int toolType = event.getToolType(0);

        //只处理 电磁笔, 手指, 擦除 toolType
        if (!(toolType == MotionEvent.TOOL_TYPE_FINGER
                || toolType == MotionEvent.TOOL_TYPE_STYLUS
                || toolType == MotionEvent.TOOL_TYPE_ERASER)) {
            return;
        }
        if (toolType == MotionEvent.TOOL_TYPE_FINGER
                && !getPWConfig().configPointer.isFingerWritable()) {
            return;
        }

        velocityTracker.addMovement(event);
        velocityTracker.computeCurrentVelocity(1);

        // 擦除和画图形时, 可以不用处理历史 pointer
        if (event.getHistorySize() > 0
                && !(thePen instanceof IPenEraser || thePen instanceof IPenShape)) {
            handlePointerOfHistory(event);
        }

        pwPointer.configBy(event);
        pwPointer.toolType = toolType;
        pwPointer.xVelocity = velocityTracker.getXVelocity();
        pwPointer.yVelocity = velocityTracker.getYVelocity();
        handlePointer(event, pwPointer);
    }

    private void handlePointerOfHistory(@NonNull MotionEvent event) {
        final int toolType = event.getToolType(0);
        final int historySize = event.getHistorySize();

        float historyPreX = Float.MIN_VALUE;
        float historyPreY = Float.MIN_VALUE;
        int step = Math.max((int) Math.ceil(1.0F * historySize / getPWConfig().configPointer.getPointerHandledHistoryMaxCount()), 1);
        for (int historyPos = 0; historyPos < historySize; historyPos += step) {

            event.getHistoricalPointerCoords(0, historyPos, mTempCoords);
            //如果 和上个坐标相差不大,我们可以直接丢弃.
            if (Math.abs(mTempCoords.x - historyPreX) < getPWConfig().configPointer.getPointerDropHistoryXYPixel()
                    && Math.abs(mTempCoords.y - historyPreY) < getPWConfig().configPointer.getPointerDropHistoryXYPixel()) {
                continue;
            }

            historyPreX = mTempCoords.x;
            historyPreY = mTempCoords.y;

            pwPointer.configBy(mTempCoords, event.getHistoricalEventTime(historyPos));
            pwPointer.toolType = toolType;
            pwPointer.xVelocity = null;
            pwPointer.yVelocity = null;
            handlePointer(event, pwPointer);
        }
    }

    /**
     * 处理点的方法, 流程是:
     * 1. 获取 pen;<br>
     * 2. 配置 paint 使用的参数;<br>
     * 3. 调用 {@link IPen#drawPointer(IPWDrawPath, PWPointer)};<br>
     * 4. 处理 {@link IPen#drawPointer(IPWDrawPath, PWPointer)} 的返回结果.<br>
     *
     * @param event   系统的 {@link MotionEvent}
     * @param pointer 使用的点 {@link PWPointer}
     */
    private void handlePointer(@NonNull MotionEvent event, @NonNull PWPointer pointer) {
        if (getPWConfig().configLog.isLogHandlePointer()) {
            Log.i(TAG, "handlePointer pointer = " + pointer);
        }

        PWTouchEventListener pwTouchEventListener = getPWConfig().configListener.getPWTouchEventListener();
        if (pwTouchEventListener != null) {
            boolean handled = pwTouchEventListener.onPWTouchEvent(event, pointer);
            if (handled) {
                return;
            }
        }

        IPen pen = null;
        if (pointer.toolType == MotionEvent.TOOL_TYPE_ERASER) {
            pen = penEraserMap.get(getPWConfig().configPen.getEraserPenIdOfCap());
        }
        if (pen == null) {
            pen = penEraserMap.get(getPWConfig().configPen.getEraserPenId());
        }
        if (pen == null) {
            pen = penWriterMap.get(getPWConfig().configPen.getWritePenId());
        }

        thePen = pen;

        if (pen != null) {

            // 配置 paint config
            getPaintConfig().color = getPWConfig().configPen.getWriteColor();
            getPaintConfig().antiAlias = getPWConfig().configPointer.isAntiAliasForPaint();
            if (pen instanceof IPenEraser) {
                getPaintConfig().strokeWidth = getPWConfig().configPen.getEraserStrokeWidth();
            } else {
                getPaintConfig().strokeWidth = getPWConfig().configPen.getWriteStrokeWidth();
            }

            // HERE IS DRAW
            PenDrawPointerResultEnums resultEnums = pen.drawPointer(this, pointer);

            Rect dirty = pen.getDirty();

            if (getPWConfig().configPointer.isInvalidateViewWhenUp()
                    && pointer.toPWPointerActionEnums() == PWPointerActionEnums.UP) {
                resultEnums = PenDrawPointerResultEnums.INVALIDATE_VIEW_ALL;
            }

            switch (resultEnums) {
                case INVALIDATE_PW_DIRTY:
                    if (dirty.intersect(bindViewDrawingRect) && !dirty.isEmpty()) {
                        //更新 底层 pw
                        PWCoreHelper.postRectForPw(dirty,
                                getPWConfig().configFreshMode.getPWDisplayMode(),
                                getPWConfig().configFreshMode.getPWA2Gate());
                    }
                    break;
                case INVALIDATE_VIEW_DIRTY:
                    if (dirty.intersect(bindViewDrawingRect) && !dirty.isEmpty()) {
                        //更新 view
                        invalidate(dirty);
                    }
                    break;
                case INVALIDATE_VIEW_ALL:
                    //更新 view
                    invalidate();
                    break;
                case NONE:
                default:
                    break;
            }

            // cache 这块没有弄好的.直接不需要了.
            if (false && getPWConfig().configCache.isEnabled()) {
                getPathCacheManager().cachePointerWithPathCache(pointer, pen.getPathCache());
            }

            if (getPWConfig().configLog.isLogDrawingPen()) {
                Log.i(TAG, "handlePointer penId = " + pen.getPenId() + ", pen = " + pen);
            }
            if (getPWConfig().configLog.isLogRectOfUpdatePW()) {
                Log.i(TAG, "postRectForPw dirty = " + dirty);
            }
        }
    }

    private void initWithView(@NonNull View view) {
        if (!view.isAttachedToWindow()) {
            Log.i(TAG, "initWithView: Not attachedToWindow for view=" + view);
            return;
        }
        final int width = view.getWidth();
        final int height = view.getHeight();

        if (width <= 0 && height <= 0) {
            Log.i(TAG, "initWithView: Size of view is not confirmed, width=" + width + ", height=" + height);
            return;
        }

        initBitmapAndCanvas(width, height);
        view.getLocationOnScreen(locationOnScreen);
        bindViewPWRect.set(locationOnScreen[0], locationOnScreen[1], locationOnScreen[0] + width, locationOnScreen[1] + height);
        view.getDrawingRect(bindViewDrawingRect);

        //初始化 pw 底层
        PWCoreHelper.initForPW();
        PWCoreHelper.setPWViewOffsetXY(bmpPW, locationOnScreen[0], locationOnScreen[1]);
        PWCoreHelper.updatePwCanvasRotation(view.getDisplay());

        setEnabledForPWListener(true);

        Log.i(TAG, "initWithView: success for view=" + view);

    }

    private void deinitWithView(@NonNull View view) {

        setEnabledForPWListener(false);

        deinitBitmapAndCanvas();

        //去初始化 pw 底层
        //230221: 调用这个 deinitForPW 导致 libeinkrect.so 报错.
//        PWCoreHelper.deinitForPW();
    }

    private void setEnabledForPWListener(boolean enabled) {
        View bindView = getBindView();

        if (bindView == null) {
            return;
        }
        Log.i(TAG, "pointerEventListener and onDrawListener: set enabled=" + enabled);

        if (enabled) {
            PWCoreHelper.setPointerEventListenerForView(bindView, this);
            PWCoreHelper.setOnDrawListenerForView(bindView, this);
        } else {
            PWCoreHelper.setPointerEventListenerForView(bindView, null);
            PWCoreHelper.setOnDrawListenerForView(bindView, null);
        }
    }

    /**
     * 执行 先 remove 再 add. 这样可以重复执行此函数.
     */
    private void configListenerForView(@NonNull View view) {
        removeListenerForView(view);
        addListenerForView(view);
    }

    private void addListenerForView(@NonNull View view) {
        view.addOnLayoutChangeListener(onLayoutChangeListener);
        view.addOnAttachStateChangeListener(onAttachStateChangeListener);
    }

    private void removeListenerForView(@NonNull View view) {
        view.removeOnLayoutChangeListener(onLayoutChangeListener);
        view.removeOnAttachStateChangeListener(onAttachStateChangeListener);
    }

    private final View.OnLayoutChangeListener onLayoutChangeListener = new View.OnLayoutChangeListener() {
        @Override
        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
            if (v == null) {
                return;
            }
            initWithView(v);
        }
    };

    private final View.OnAttachStateChangeListener onAttachStateChangeListener = new View.OnAttachStateChangeListener() {
        @Override
        public void onViewAttachedToWindow(@NonNull View v) {
            initWithView(v);
        }

        @Override
        public void onViewDetachedFromWindow(@NonNull View v) {
            deinitWithView(v);
        }
    };


}
